package org.dei.perla.device.network;

import java.util.ArrayList;

import org.dei.perla.component.Component;
import org.dei.perla.sys.configuration.ConfigurationManager;
import org.dei.perla.sys.device.adapter.AdapterServerConcrete;
import org.dei.perla.sys.device.adapter.Gateway;
import org.dei.perla.sys.device.channel.ChannelManager;
import org.dei.perla.sys.device.channel.concrete.socket.SocketChannelManager;
import org.dei.perla.sys.device.channel.frame.address.concrete.SocketFrameAddress;
import org.dei.perla.sys.device.fpcfactory.FpcFactory;
import org.dei.perla.utils.logger.ConsoleLogger;
import org.dei.perla.utils.logger.LogRecord;
import org.dei.perla.utils.logger.Logger;
import org.dei.perla.utils.logger.LogRecord.Verbosity;
import org.dei.perla.utils.messages.AdapterChannelMessage;
import org.dei.perla.utils.messages.MsgUtility;


/**
 * Test di una rete PERLA
 * 
 * La rete simulata è composta da cinque nodi sensori (1a,2a,1b,2b,3),
 * un nodo gateway e un nodo con macchina FPC.
 * 
 * I vari nodi sono interconnessi in questo modo
 * 
 * 1a |
 *    |------| 
 * 2a |      |
 *           | Gateway | ---- | 
 * 1b |      |                |
 *    | -----|                | FPC-Factory
 * 2b |                       |
 *                  3 | ------|
 * 
 * 
 * Il test dimostra il binding dei sensori e il corretto funzionamento del binding
 * anche attraverso il nodo Gateway.
 * 
 */
public class TestNetworkChannel {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		// Istanzia il logger
		ConsoleLogger cl = new ConsoleLogger();
		cl.setVerbosity(Verbosity.Medium);
		cl.start();
		
		// Crea un thread per ogni macchina simulata
		Thread nodeFPC = new Thread(new NodeFPC(cl, "FPC", "config-socket-node-FPC.xml"));
		Thread nodeG = new Thread(new NodeGateway(cl, "Gateway", "config-socket-node-gateway.xml" ));
		Thread nodeS1a = new Thread(new NodeSensor(cl, "S 1a","config-socket-node-sensor1a.xml", "127.0.0.1:4000", 1));
		Thread nodeS1b = new Thread(new NodeSensor(cl, "S 1b","config-socket-node-sensor1b.xml", "127.0.0.1:5000", 2));
		Thread nodeS2a = new Thread(new NodeSensor(cl, "S 2a","config-socket-node-sensor2a.xml", "127.0.0.1:4000", 1));
		Thread nodeS2b = new Thread(new NodeSensor(cl, "S 2b","config-socket-node-sensor2b.xml", "127.0.0.1:5000", 2));
		Thread nodeS3 = new Thread(new NodeSensor(cl, "S 3","config-socket-node-sensor3.xml", "127.0.0.1:7000", 2));
		
		// avvia i thread
		nodeFPC.start();
		nodeG.start();
		nodeS1a.start();
		nodeS2a.start();
		nodeS1b.start();
		nodeS2b.start();
		nodeS3.start();
		
		try {
			nodeFPC.join();
		} catch (InterruptedException e) {}
		
		cl.stop();
		System.out.printf("Test finito!\n");
		
	}
	
	static private class NodeFPC implements Runnable {
		
		private ArrayList<ChannelManager> CM;
		
		NodeFPC(Logger l, String name, String config) {
			
			// Inizializza il nodo con FPC factory
			ConfigurationManager conf = new ConfigurationManager(config);
			conf.loggerAttach(l);
			
			// Leggi la configurazione
			conf.start();
			CM = conf.getChannels();
			CM.get(0).loggerAttach(l);
			CM.get(0).start();
			
			// Crea l'adapter server
			AdapterServerConcrete A = new AdapterServerConcrete("Adapter server", CM);
			A.loggerAttach(l);
			A.start();
			
			// Crea l'FPC factory
			FpcFactory F = new FpcFactory (A);
			F.loggerAttach(l);
			F.start();
		}
		
		public void run() {

			
		}
		
	}
	
	static private class NodeGateway implements Runnable {
	
		private ConfigurationManager conf;
		private Gateway G;
		private ArrayList<ChannelManager> CM;
		
		NodeGateway(Logger l,String name, String config) {
			// Inizializza il nodo gateway
			conf = new ConfigurationManager(config);
			conf.loggerAttach(l);
			
			// Leggi la configurazione
			conf.start();
			CM = conf.getChannels();
			G = conf.getGateway();
			G.loggerAttach(l);
			
			for (int i = 0; i < CM.size(); i++) {
				CM.get(i).loggerAttach(l);
				CM.get(i).start();
			}
			
			for (int i = 0; i < CM.size(); i++) {
				CM.get(i).start();
			}
			G.start();
		}
		
		public void run() {
			

		}
		
	}

	static private class NodeSensor extends Component implements Runnable {

		private String bindAddress;
		private int deviceType;
		private SocketChannelManager CM;

		NodeSensor(Logger parLogger, String name, String config, String bindAddress, int deviceType) {
			
			super(name);
			this.bindAddress = bindAddress;
			this.deviceType = deviceType;
			
			// Inizializza il nodo sensore
			ConfigurationManager conf = new ConfigurationManager(config);
			conf.loggerAttach(parLogger);
			
			// Leggi la configurazione
			conf.start();
			CM = (SocketChannelManager) conf.getChannels().get(0);
			CM.loggerAttach(parLogger);
			CM.start();
			this.loggerAttach(parLogger);
		}
		
		public void run() {
			
			AdapterChannelMessage m = createBindingMessage(1, bindAddress, deviceType);
			this.loggerNotify(LogRecord.Type.Information, "Sending message", m.toString(), LogRecord.Verbosity.Medium);
	        CM.getOutputPipe().enqueue(m);
	       
	        m = CM.getInputPipe().dequeue();
	        while(m == null) {
	        	m = CM.getInputPipe().dequeue();
	        }
	        
	        this.loggerNotify(LogRecord.Type.Information, "Received message", m.toString(), LogRecord.Verbosity.Medium);
			
	        AdapterChannelMessage m1 = createNormalMessage (MsgUtility.getVciFromAckMessage(m.getPayload()), this.deviceType, bindAddress);
	        this.loggerNotify(LogRecord.Type.Information, "Sending message", m.toString(), LogRecord.Verbosity.Medium);
	        CM.getOutputPipe().enqueue(m1);

	        m = CM.getInputPipe().dequeue();
	        while(m == null) {
	        	m = CM.getInputPipe().dequeue();
	        }
	        
	        this.loggerNotify(LogRecord.Type.Information, "Received message", m.toString(), LogRecord.Verbosity.Medium);
	     
	        this.loggerNotify(LogRecord.Type.Information, "Job done!", LogRecord.Verbosity.Medium);
		}
		
		/**
		 * Crea un messaggio di binding
		 * @param bvci
		 * @param address
		 * @param deviceType
		 * @return
		 */
		private AdapterChannelMessage createBindingMessage (int bvci, String address, int deviceType){
	         
	         SocketFrameAddress add = new SocketFrameAddress(address);
	         
	         byte[] flagByte = MsgUtility.intToByteArray(MsgUtility.flagBinding);
	         byte[] bvciByte = MsgUtility.intToByteArray((int) (Math.random()*2000000));
	         
	         
	         String XMLdescriptor = "";
	         if ( deviceType == 1 ) {
	        	 XMLdescriptor = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
	        	 					"<deviceType>1</deviceType>\n";
	         } else if ( deviceType == 2 ) {
	        	 XMLdescriptor = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
					"<deviceType>2</deviceType>\n";
	         }

	         byte[] descByte = XMLdescriptor.getBytes();
	         
	         byte[] payload = new byte[MsgUtility.flagLength + bvciByte.length + descByte.length];
	        
	         // Flag
	         for(int i = 0; i < MsgUtility.flagLength; i++) {
	        	 payload[i] = flagByte[i];     
	         }
	         
	         // Bvci
	         for (int i = MsgUtility.flagLength; i < MsgUtility.flagLength + MsgUtility.channelIdLength; i++) {
	        	 payload[i] = bvciByte[i-4];
	         }
	         
	         // messaggio
	         for(int i = MsgUtility.flagLength + MsgUtility.channelIdLength; i < MsgUtility.flagLength + descByte.length + bvciByte.length; i++){
	        	 payload[i] = descByte[i-8];
	        	 
	         }
	         
	         return new AdapterChannelMessage(add, payload);

		 }
		
		/**
		 * Crea un esempio di un messaggio di normale comunicazione
		 * tra sensore e FPC
		 * @param vci
		 * @param deviceType
		 * @param address
		 * @return
		 */
		private AdapterChannelMessage createNormalMessage (int vci, int deviceType, String address){
	         
	         SocketFrameAddress add = new SocketFrameAddress(address);
	         
	         byte[] flagByte = MsgUtility.intToByteArray(MsgUtility.flagOthers);
	         
	         String message = "";
	         if ( deviceType == 1 ) {
	        	 message = "Comunicazione device 1!";
	         } else if ( deviceType == 2 ) {
	        	 message = "Comunicazione device 2!";
	         }
	         
	         byte [] mex = message.getBytes();
	         
	         
	         byte[] vciByte = MsgUtility.intToByteArray(vci);
	         
	         byte [] payload = new byte [MsgUtility.flagLength + vciByte.length + mex.length];
	         
	         // Flag
	         for(int i = 0; i < MsgUtility.flagLength; i++) {
	        	 payload[i] = flagByte[i];     
	         }
	         
	         // Vci
	         for (int i = MsgUtility.flagLength; i < MsgUtility.flagLength + MsgUtility.channelIdLength; i++) {
	        	 payload[i] = vciByte[i-4];
	         }
		        
	         // messaggio
	         for(int i = MsgUtility.flagLength + MsgUtility.channelIdLength; i < MsgUtility.flagLength + vciByte.length + mex.length; i++){
	        	 payload[i] = mex[i-8];
	        	 
	         }
	         
	         return new AdapterChannelMessage(add, payload);

		 }
		
	}
         
 }
